use serde::{Deserialize, Serialize};
use std::io;
use tracing::Level;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

/// 日志配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    pub level: String,
    pub format: String,
    pub file_enabled: bool,
    pub file_path: String,
    pub file_rotation: String,
}

impl Default for LoggingConfig {
    fn default() -> Self {
        Self {
            level: "info".to_string(),
            format: "pretty".to_string(),
            file_enabled: true,
            file_path: "./logs/app.log".to_string(),
            file_rotation: "daily".to_string(),
        }
    }
}

/// 日志初始化错误
#[derive(Debug)]
pub enum LoggingError {
    InvalidLevel(String),
    InvalidRotation(String),
    InvalidFormat(String),
    FileCreation(io::Error),
    Setup(String),
}

impl std::fmt::Display for LoggingError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LoggingError::InvalidLevel(level) => {
                write!(
                    f,
                    "Invalid log level: {}, valid values are: error, warn, info, debug, trace",
                    level
                )
            }
            LoggingError::InvalidRotation(rotation) => {
                write!(
                    f,
                    "Invalid rotation strategy: {}, valid values are: daily, hourly, never",
                    rotation
                )
            }
            LoggingError::InvalidFormat(format) => {
                write!(
                    f,
                    "Invalid log format: {}, valid values are: pretty, json",
                    format
                )
            }
            LoggingError::FileCreation(err) => {
                write!(f, "Failed to create log file: {}", err)
            }
            LoggingError::Setup(msg) => {
                write!(f, "Failed to initialize logging system: {}", msg)
            }
        }
    }
}

impl std::error::Error for LoggingError {}

fn parse_level(level: &str) -> Result<Level, LoggingError> {
    match level.to_lowercase().as_str() {
        "error" => Ok(Level::ERROR),
        "warn" => Ok(Level::WARN),
        "info" => Ok(Level::INFO),
        "debug" => Ok(Level::DEBUG),
        "trace" => Ok(Level::TRACE),
        _ => Err(LoggingError::InvalidLevel(level.to_string())),
    }
}

/// 初始化 tracing 日志系统
pub fn setup_tracing(
    config: &LoggingConfig,
) -> Result<Option<WorkerGuard>, Box<dyn std::error::Error + Send + Sync>> {
    let level = parse_level(&config.level)?;
    let env_filter =
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level.to_string()));

    if config.file_enabled {
        let (file_appender, guard) = create_file_appender(config)?;

        match config.format.to_lowercase().as_str() {
            "pretty" => {
                tracing_subscriber::registry()
                    .with(env_filter)
                    .with(
                        fmt::layer()
                            .with_writer(io::stdout)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .pretty(),
                    )
                    .with(
                        fmt::layer()
                            .with_writer(file_appender)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .with_ansi(false)
                            .pretty(),
                    )
                    .try_init()
                    .map_err(|e| LoggingError::Setup(e.to_string()))?;
            }
            "json" => {
                tracing_subscriber::registry()
                    .with(env_filter)
                    .with(
                        fmt::layer()
                            .with_writer(io::stdout)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .json(),
                    )
                    .with(
                        fmt::layer()
                            .with_writer(file_appender)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .json(),
                    )
                    .try_init()
                    .map_err(|e| LoggingError::Setup(e.to_string()))?;
            }
            _ => return Err(Box::new(LoggingError::InvalidFormat(config.format.clone()))),
        }
        Ok(Some(guard))
    } else {
        match config.format.to_lowercase().as_str() {
            "pretty" => {
                tracing_subscriber::registry()
                    .with(env_filter)
                    .with(
                        fmt::layer()
                            .with_writer(io::stdout)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .pretty(),
                    )
                    .try_init()
                    .map_err(|e| LoggingError::Setup(e.to_string()))?;
            }
            "json" => {
                tracing_subscriber::registry()
                    .with(env_filter)
                    .with(
                        fmt::layer()
                            .with_writer(io::stdout)
                            .with_target(true)
                            .with_line_number(true)
                            .with_file(true)
                            .json(),
                    )
                    .try_init()
                    .map_err(|e| LoggingError::Setup(e.to_string()))?;
            }
            _ => return Err(Box::new(LoggingError::InvalidFormat(config.format.clone()))),
        }
        Ok(None)
    }
}

fn create_file_appender(
    config: &LoggingConfig,
) -> Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard), Box<dyn std::error::Error + Send + Sync>>
{
    let file_path = std::path::Path::new(&config.file_path);
    let directory = file_path
        .parent()
        .unwrap_or_else(|| std::path::Path::new("."));
    let file_prefix = file_path
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("app");

    std::fs::create_dir_all(directory).map_err(LoggingError::FileCreation)?;

    let file_appender = match config.file_rotation.to_lowercase().as_str() {
        "daily" => tracing_appender::rolling::daily(directory, file_prefix),
        "hourly" => tracing_appender::rolling::hourly(directory, file_prefix),
        "never" => tracing_appender::rolling::never(directory, file_prefix),
        _ => {
            return Err(Box::new(LoggingError::InvalidRotation(
                config.file_rotation.clone(),
            )));
        }
    };

    let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);

    Ok((non_blocking, guard))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_level() {
        assert!(matches!(parse_level("error"), Ok(Level::ERROR)));
        assert!(matches!(parse_level("warn"), Ok(Level::WARN)));
        assert!(matches!(parse_level("info"), Ok(Level::INFO)));
        assert!(matches!(parse_level("debug"), Ok(Level::DEBUG)));
        assert!(matches!(parse_level("trace"), Ok(Level::TRACE)));

        assert!(matches!(parse_level("ERROR"), Ok(Level::ERROR)));
        assert!(matches!(parse_level("Info"), Ok(Level::INFO)));

        assert!(matches!(
            parse_level("invalid"),
            Err(LoggingError::InvalidLevel(_))
        ));
    }

    #[test]
    fn test_logging_error_display() {
        let err = LoggingError::InvalidLevel("foo".to_string());
        assert!(err.to_string().contains("Invalid log level"));
        assert!(err.to_string().contains("foo"));

        let err = LoggingError::InvalidRotation("bar".to_string());
        assert!(err.to_string().contains("Invalid rotation strategy"));
        assert!(err.to_string().contains("bar"));

        let err = LoggingError::InvalidFormat("baz".to_string());
        assert!(err.to_string().contains("Invalid log format"));
        assert!(err.to_string().contains("baz"));
    }
}
